#include "GLTFLoader.h"
#include "OCMain/DataStream.h"
#include "OCMain/StringUtil.h"

#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_NO_STB_IMAGE
#define TINYGLTF_NO_STB_IMAGE_WRITE

#include "tiny_gltf.h"


namespace OCPP
{
	bool myImageLoader(tinygltf::Image* outImage, const int image_idx, String* err,
		String* warn, int, int,
		const unsigned char* data, int dataSize,
		void* user_pointer)
	{
		//String baseName, ext;
		//StringUtil::splitBaseFilename(outImage->uri, baseName, ext);
		//MemoryDataStreamPtr encoded(new MemoryDataStream(dataSize));
		//encoded->write(data, dataSize);
		//encoded->seek(0);

		//OCPP::Image image;
		//image.load(encoded, ext);


		////image->width = w;
		////image->height = h;
		////image->component = comp;
		////image->bits = bits;
		////image->pixel_type = pixel_type;

		//outImage->width = image.getWidth();
		//outImage->height = image.getHeight();
		//outImage->bits = PixelUtil::getNumElemBits(image.getFormat());
		//outImage->pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
		//outImage->component = PixelUtil::getComponentCount(image.getFormat());

		//PixelComponentType pct = PixelUtil::getComponentType(image.getFormat());
		//if (pct == PCT_SHORT)
		//{
		//	outImage->pixel_type = TINYGLTF_COMPONENT_TYPE_SHORT;
		//}
		//else if (pct == PCT_FLOAT16 || pct == PCT_FLOAT32)
		//{
		//	outImage->pixel_type = TINYGLTF_COMPONENT_TYPE_FLOAT;
		//}

		//size_t size = image.getSize();
		//outImage->image.resize(size);
		//memcpy(&outImage->image[0], image.getData(), size);

		return true;
	}

	Matrix4 getMatrix(tinygltf::Node& node)
	{
		auto& m = node.matrix;
		if (m.size() == 16)
		{
			return Matrix4(
				(float)m[0], (float)m[2], (float)m[3], (float)m[4],
				(float)m[5], (float)m[6], (float)m[7], (float)m[8],
				(float)m[9], (float)m[10], (float)m[11], (float)m[12],
				(float)m[12], (float)m[13], (float)m[14], (float)m[15]);
		}
		return Matrix4::IDENTITY;
	}

	Uint32Vector getUInts(tinygltf::Model& model, int accessorId)
	{
		Uint32Vector uintVec;

		if (accessorId < 0)
		{
			return uintVec;
		}

		tinygltf::Accessor& accessor = model.accessors[accessorId];
		tinygltf::BufferView& bufferView = model.bufferViews[accessor.bufferView];
		tinygltf::Buffer& buffer = model.buffers[bufferView.buffer];
		uint32 count = accessor.count;

		int accByteOffset = accessor.byteOffset;
		unsigned int byteOffset = bufferView.byteOffset;

		// Go over all the bytes in the data at the correct place using the properties from above
		unsigned int beginningOfData = byteOffset + accByteOffset;

		uint8* data = &buffer.data[0];

		int componentType = accessor.componentType;
		if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)
		{
			for (unsigned int i = beginningOfData; i < byteOffset + accByteOffset + count * 4; i)
			{
				unsigned char bytes[] = { data[i++], data[i++], data[i++], data[i++] };
				unsigned int value;
				std::memcpy(&value, bytes, sizeof(unsigned int));
				uintVec.push_back((GLuint)value);
			}
		}
		else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT)
		{
			for (unsigned int i = beginningOfData; i < byteOffset + accByteOffset + count * 2; i)
			{
				unsigned char bytes[] = { data[i++], data[i++] };
				unsigned short value;
				std::memcpy(&value, bytes, sizeof(unsigned short));
				uintVec.push_back((GLuint)value);
			}
		}
		else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT)
		{
			for (unsigned int i = beginningOfData; i < byteOffset + accByteOffset + count * 2; i)
			{
				unsigned char bytes[] = { data[i++], data[i++] };
				short value;
				std::memcpy(&value, bytes, sizeof(short));
				uintVec.push_back((GLuint)value);
			}
		}

		return uintVec;
	}

	FloatVector getFloats(tinygltf::Model& model, int accessorId)
	{
		FloatVector floatVec;
		if (accessorId < 0)
		{
			return floatVec;
		}
		tinygltf::Accessor& accessor = model.accessors[accessorId];
		tinygltf::BufferView& bufferView = model.bufferViews[accessor.bufferView];
		tinygltf::Buffer& buffer = model.buffers[bufferView.buffer];
		uint32 count = accessor.count;

		int type = accessor.type;

		int numPerVert;
		if (type == TINYGLTF_TYPE_SCALAR) numPerVert = 1;
		else if (type == TINYGLTF_TYPE_VEC2) numPerVert = 2;
		else if (type == TINYGLTF_TYPE_VEC3) numPerVert = 3;
		else if (type == TINYGLTF_TYPE_VEC4) numPerVert = 4;
		else throw std::invalid_argument("Type is invalid (not SCALAR, VEC2, VEC3, or VEC4)");

		unsigned int accByteOffset = accessor.byteOffset;
		unsigned int byteOffset = bufferView.byteOffset;

		// Go over all the bytes in the data at the correct place using the properties from above
		unsigned int beginningOfData = byteOffset + accByteOffset;

		uint32 floatCount = count * numPerVert;

		unsigned int lengthOfData = floatCount * sizeof(float);

		floatVec.resize(floatCount);

		std::memcpy(&floatVec[0], &model.buffers[0].data[beginningOfData], lengthOfData);

		return floatVec;
	}

	GLTFLoader::GLTFLoader()
	{
		
	}

	GLTFLoader::~GLTFLoader()
	{

	}

	MeshList& GLTFLoader::getMeshes() 
	{
		return mMeshList;
	}

	void GLTFLoader::load(String fileName)
	{
		tinygltf::TinyGLTF loader;
		loader.SetImageLoader(myImageLoader, NULL);

		String err;
		String warn;
		tinygltf::Model model;
		bool loaded = loader.LoadASCIIFromFile(&model, &err, &warn, fileName);

		if (!loaded)
		{
			return;
		}

		String name11, ext11;
		StringUtil::splitFullFilename(fileName, name11, ext11, mFilePath);

		for (auto scene : model.scenes)
		{
			for (auto n : scene.nodes)
			{
				traverse(model, n, Matrix4::IDENTITY);
			}
		}
	}

	TextureList GLTFLoader::getMaterials(tinygltf::Model& model, int matInd)
	{
		TextureList textures;

		if (matInd < model.materials.size())
		{
			tinygltf::Material& material = model.materials[matInd];

			auto& baseColorTexture = material.pbrMetallicRoughness.baseColorTexture;
			if (baseColorTexture.index > -1 && baseColorTexture.index < model.textures.size())
			{
				auto& textureInfo = model.textures[baseColorTexture.index];
				if (textureInfo.source > -1 && textureInfo.source < model.images.size())
				{
					auto& image = model.images[textureInfo.source];
					String imageFileName = mFilePath + image.uri;
					textures.push_back(std::make_shared<Texture>(imageFileName, GL_TEXTURE_2D, "diffuse"));
				}
			}

			auto& metallicRoughnessTexture = material.pbrMetallicRoughness.metallicRoughnessTexture;
			if (metallicRoughnessTexture.index > -1 && metallicRoughnessTexture.index < model.textures.size())
			{
				auto& textureInfo = model.textures[metallicRoughnessTexture.index];
				if (textureInfo.source > -1 && textureInfo.source < model.images.size())
				{
					auto& image = model.images[textureInfo.source];
					String imageFileName = mFilePath + image.uri;
					textures.push_back(std::make_shared<Texture>(imageFileName, GL_TEXTURE_2D, "specular"));
				}
			}
		}

		return textures;
	}

	void GLTFLoader::traverse(tinygltf::Model& model, int nodeInd, Matrix4 parentMatrix)
	{
		if (nodeInd < 0)
		{
			return;
		}

		tinygltf::Node& node = model.nodes[nodeInd];

		auto& rotation = node.rotation;
		auto& translation = node.translation;
		auto& scale = node.scale;

		Quaternion quat = Quaternion::IDENTITY;
		Vector3 transVec = Vector3::ZERO;
		Vector3 scaleVec = Vector3::UNIT_SCALE;

		if (rotation.size() == 4)
		{
			quat.w = rotation[3]; quat.x = rotation[0]; quat.y = rotation[1]; quat.z = rotation[2];
		}
		if (translation.size() == 3)
		{
			transVec.x = translation[0], transVec.x = translation[1], transVec.x = translation[2];
		}
		if (scale.size() == 3)
		{
			scaleVec.x = scale[0], scaleVec.x = scale[1], scaleVec.x = scale[2];
		}

		Matrix4 matrixState;

		matrixState.makeTransform(transVec, scaleVec, quat);

		Matrix4 matrix = parentMatrix * matrixState * getMatrix(node);

		if (node.mesh > -1)
		{
			auto m = model.meshes[node.mesh];

			for (auto p : m.primitives)
			{
				int posAccInd = p.attributes["POSITION"];
				int normalAccInd = p.attributes["NORMAL"];
				int colorAccInd = p.attributes.find("COLOR") != p.attributes.end() ? p.attributes["COLOR"] : -1;
				int texAccInd = p.attributes["TEXCOORD_0"];
				int indAccInd = p.indices;

				FloatVector posFloats = getFloats(model, posAccInd);
				FloatVector norFloats = getFloats(model, normalAccInd);
				FloatVector clrFloats = getFloats(model, colorAccInd);
				FloatVector texFloats = getFloats(model, texAccInd);

				VertexList vertices;

				size_t size = posFloats.size() / 3;

				for (size_t i = 0; i < size; i++)
				{
					size_t ip = i * 3;
					size_t it = i * 2;
					vertices.push_back(
						{
							{posFloats[ip],posFloats[ip + 1],posFloats[ip + 2]},
							{norFloats[ip],norFloats[ip + 1],norFloats[ip + 2]},
							{},
							{texFloats[it],texFloats[it + 1]},
						}
					);
				}

				IndiceList indices = getUInts(model, indAccInd);

				TextureList textures = getMaterials(model, p.material);

				mMeshList.push_back(std::make_shared<Mesh>(vertices, indices, textures, mInstanceMatrixs));
				mMeshList[mMeshList.size() - 1]->setMatirx(matrix);
			}

		}

		for (auto child : node.children)
		{
			traverse(model, child, matrix);
		}
	}
}